#ifndef DEVICE_LIST_H
#define DEVICE_LIST_H

#include "stdafx.h"
#include "CL/cl.hpp"
#include <ppl.h>
namespace RayTracePro
{
	using namespace Concurrency;
	class DeviceListArrayView
	{
	public:
		bool bufferInvalid;
		bool contentModified;
		DeviceListArrayView()
		{
			bufferInvalid = true;
			contentModified = false;
		}
		template<typename T>
		inline void BufferAllocated(T*newBuffer, int size)
		{
			bufferInvalid = true;
		}
		inline void DataModified()
		{
			contentModified = true;
		}
	};

	class DeviceMemoryAccess
	{
	public:
		enum Enum
		{
			ReadWrite, ReadOnly, WriteOnly
		};
	};

	template <typename T>
	class DeviceList : public GxLibBasic::Array<T,true,GxLibBasic::gxStandardGrowStrategy, DeviceListArrayView>
	{
	private:
		cl::Buffer deviceBuffer;
		int deviceBufferSize;
		DeviceMemoryAccess::Enum devAccess;
	public:
		DeviceList(DeviceMemoryAccess::Enum access = DeviceMemoryAccess::ReadWrite)
		{
			deviceBufferSize = -1;
			devAccess = access;
		}
		cl::Buffer & DeviceBuffer(bool copyHostData = true)
		{
			Refresh(copyHostData);
			return deviceBuffer;
		}
		void Refresh(bool copyHostData = true, bool blocking = true)
		{
			if (eventHandler.bufferInvalid || FCapacity != deviceBufferSize)
			{
				eventHandler.bufferInvalid = false;
				deviceBufferSize = FCapacity;
				cl_mem_flags flag;
				if (devAccess == DeviceMemoryAccess::ReadOnly)
					flag = CL_MEM_READ_ONLY;
				else if (devAccess == DeviceMemoryAccess::WriteOnly)
					flag = CL_MEM_WRITE_ONLY;
				else
					flag = CL_MEM_READ_WRITE;
				if (copyHostData && FCapacity > 0)
					deviceBuffer = cl::Buffer(GpuDevice::GetContext(), flag|CL_MEM_USE_HOST_PTR, FCapacity*sizeof(T), FItems);
				else
					deviceBuffer = cl::Buffer(GpuDevice::GetContext(), flag, max(FCapacity, 1)*sizeof(T), NULL);
				return;
			}
			else if (copyHostData && FCount > 0)
			{	
				GpuDevice::GetQueue().enqueueWriteBuffer(deviceBuffer, blocking, 0, FCount*sizeof(T), FItems); 
			}
			eventHandler.contentModified = false;
		}
		void Synchronize(bool blocking = true)
		{
			if (FCount > 0)
				GpuDevice::GetQueue().enqueueReadBuffer(deviceBuffer, blocking, 0, FCount*sizeof(T), FItems); 
		}
	};

	// Arrange list to array of structure
	template <typename T>
	void ArrangeAsArrayOfStructure(DeviceList<T> & list)
	{
		int size = sizeof(T)/sizeof(int);
		int * values = new int[list.Count()*size];
		memcpy(values, list.Buffer(), list.Count()*size*sizeof(int));
		for (int i = 0; i<list.Count(); i++)
		{
			int * tar = (int *)&list[i];
			for (int j = 0; j<size; j++)
				tar[j] = values[j*list.Count()+i];
		}
		delete [] values;
	}

	// arrange list to structure of array
	template <typename T>
	void ArrangeAsStructureOfArray(DeviceList<T> & list)
	{
		int size = sizeof(T)/sizeof(int);
		int * values = new int[list.Count()*size];
		int * tar = (int*)list.Buffer();
		memcpy(values, list.Buffer(), list.Count()*size*sizeof(int));
		for (int j = 0; j<size; j++)
		{
			for (int i = 0; i<list.Count(); i++)
			{
				tar[j*list.Count()+i] = values[i*size + j];
			}
		}
		delete [] values;
	}

	template<typename T, typename SumFunc>	
	T ExclusiveScan(DeviceList<T> & values, const T& identity, SumFunc f)
	{
		T rs = 0;
		for (int i = 0; i<values.Count(); i++)
		{
			T vi = values[i];
			values[i] = rs;
			rs = f(rs, vi);
		}
		return rs;
	}

	inline int ExclusiveScan(DeviceList<int> & list)
	{
		return ExclusiveScan(list, 0, [](const int a, const int b){return a+b;});
	}

}

#endif